package com.example;

import org.opencv.core.*;
import org.opencv.features2d.*;
import org.opencv.calib3d.Calib3d;
import org.opencv.imgproc.Imgproc;

import java.util.*;

/**
 * <p>图像拼接</p>
 *
 * @author dc
 * @date 2025-09-25
 **/
public class ImageStitcher {

    /**
     * 滚动拼接
     */
    public static Mat rollStitchImages(List<Mat> images) {
        Mat result = stitchTwoImages(images.get(0), images.get(1));
        for (int i = 2; i < images.size(); i++) {
            result = stitchTwoImages(result, images.get(i));
        }
        return result;
    }
    
    /**
     * 拼接两张图
     */
    public static Mat stitchTwoImages(Mat img1, Mat img2) {
        // 获取单应性矩阵
        Mat H = getHomography(img1, img2);
        
        Mat result = new Mat();
        Size size = new Size(Math.max(img1.cols(), img2.cols()), img1.rows() + img2.rows());
        Imgproc.warpPerspective(img1, result, H, size);

        System.out.println("拼接尺寸:"+ img1.cols() + ":"+ img1.rows() + ":"+ img2.cols() + ":"+ img2.rows());
        // 将第二张图像拷贝到结果中
        Mat roi = result.submat(img1.rows(), img1.rows() + img2.rows(), 0, img2.cols());
        img2.copyTo(roi);
        System.out.println("拼接result尺寸:"+ result.cols() + ":"+ result.rows());
        
        H.release();
        roi.release();
        
        return result;
    }
    

    /**
     * 主拼接方法（去除重复部分）
     */
    public static Mat stitchImages(List<Mat> images) {
        System.out.println("图片列表长度:"+images.size());
        if (images == null || images.isEmpty())  {
            throw new IllegalArgumentException("图像列表不能为空");
        }

        // 第一张图像作为基准 
        Mat result = images.get(0).clone();

        // 遍历后续图像进行拼接
        for (int i = 1; i < images.size();  i++) {
            Mat nextImage = images.get(i);

            // 1. 检测重叠区域
            OverlapInfo overlap = findOverlapRegion(result, nextImage);

            // 2. 智能裁剪和拼接
            if (overlap.height  > 0) {
                System.out.println("有重叠区域:"+overlap.toString());
                // 裁剪掉重叠部分 
                Rect roi = new Rect(0, overlap.y + overlap.height,
                        nextImage.cols(),
                        nextImage.rows()  - overlap.height);
                Mat nonOverlapPart = new Mat(nextImage, roi);

                // 垂直拼接 
                Core.vconcat(Arrays.asList(result,  nonOverlapPart), result);
            } else {
                // 无重叠部分时直接拼接 
                Core.vconcat(Arrays.asList(result,  nextImage), result);
            }
        }

        return result;
    }

    /**
     * 获得单应性矩阵
     */
    public static Mat getHomography(Mat img1, Mat img2) {
        // 使用ORB特征检测器（效率与精度平衡）
        ORB orb = ORB.create();

        // 检测特征点和描述符 
        MatOfKeyPoint keypoints1 = new MatOfKeyPoint();
        MatOfKeyPoint keypoints2 = new MatOfKeyPoint();
        Mat descriptors1 = new Mat();
        Mat descriptors2 = new Mat();

        orb.detectAndCompute(img1,  new Mat(), keypoints1, descriptors1);
        orb.detectAndCompute(img2,  new Mat(), keypoints2, descriptors2);

        // 特征匹配 
        DescriptorMatcher matcher = DescriptorMatcher.create(
                DescriptorMatcher.BRUTEFORCE_HAMMING);
        MatOfDMatch matches = new MatOfDMatch();
        matcher.match(descriptors1,  descriptors2, matches);

        // 筛选优质匹配点 
        List<DMatch> matchList = matches.toList();
        double minDist = Double.MAX_VALUE;
        for (DMatch match : matchList) {
            minDist = Math.min(minDist,  match.distance);
        }

        List<DMatch> goodMatches = new ArrayList<>();
        for (DMatch match : matchList) {
            if (match.distance  < Math.max(2  * minDist, 30.0)) {
                goodMatches.add(match);
            }
        }

        // 提取匹配点坐标
        List<Point> pts1 = new ArrayList<>();
        List<Point> pts2 = new ArrayList<>();
        for (DMatch match : goodMatches) {
            pts1.add(keypoints1.toList().get(match.queryIdx).pt);
            pts2.add(keypoints2.toList().get(match.trainIdx).pt);
        }

        // 计算单应性矩阵
        Mat homography = Calib3d.findHomography(
                new MatOfPoint2f(pts2.toArray(new  Point[0])),
                new MatOfPoint2f(pts1.toArray(new  Point[0])),
                Calib3d.RANSAC, 3);

        return homography;
    }

    
    
    public static OverlapInfo findOverlapRegion(Mat img1, Mat img2) {
        // 获取单应性矩阵
        Mat homography = getHomography(img1, img2);

        // 计算重叠区域
        OverlapInfo overlap = calculateOverlapHeight(homography, img1.rows(),  img2.rows());
        return overlap;
    }
    /**
     * 计算重叠高度
     */
    private static OverlapInfo calculateOverlapHeight(Mat homography, int height1, int height2) {
        if (homography.empty())  {
            return new OverlapInfo(0, 0, 0);
        }

        // 创建齐次坐标点 (0, height2-1, 1)
        Mat pt = new Mat(3, 1, CvType.CV_64FC1);
        pt.put(0,  0, 0);
        pt.put(1,  0, height2 - 1);
        pt.put(2,  0, 1);

        Mat transformedPt = new Mat();
        try {
            // 执行矩阵乘法：H * pt
            Core.gemm(homography,  pt, 1, new Mat(), 0, transformedPt);

            // 正确获取变换后的点坐标
            double w = transformedPt.get(2,  0)[0];
            // 防止除以零 
            if (Math.abs(w)  < 1e-10) {
                w = 1e-10;
            }

            double x = transformedPt.get(0,  0)[0] / w;
            double y = transformedPt.get(1,  0)[0] / w;

            // 计算重叠区域
            int overlapStart = (int) Math.max(0,  y);
            int overlapHeight = height1 - overlapStart;

            // 验证重叠高度是否有效 
            if (overlapHeight <= 0 || overlapHeight > height2) {
                return new OverlapInfo(0, 0, 0);
            }

            return new OverlapInfo(overlapStart, 0, overlapHeight);
        } finally {
            // 清理资源 
            pt.release();
            transformedPt.release();
        }
    }

    /**
     * 重叠区域信息类 
     */
    public static class OverlapInfo {
        int y;          // 重叠区域在第二张图像中的Y坐标
        int x;          // 重叠区域在第二张图像中的X坐标
        int height;     // 重叠区域高度 

        OverlapInfo(int y, int x, int height) {
            this.y = y;
            this.x = x;
            this.height  = height;
        }

        public int getY() {
            return y;
        }

        public void setY(int y) {
            this.y = y;
        }

        public int getX() {
            return x;
        }

        public void setX(int x) {
            this.x = x;
        }

        public int getHeight() {
            return height;
        }

        public void setHeight(int height) {
            this.height = height;
        }

        @Override
        public String toString() {
            return "OverlapInfo{" +
                    "y=" + y +
                    ", x=" + x +
                    ", height=" + height +
                    '}';
        }
    }
}